#pragma once

#include "../BaseTracker.h"
#include "../BaseDepth.h"

namespace mdtx::punch::smatch
{
    /**
     * @brief Bookdepth tracker
     *
     * @tparam QT quote type
     */
    template <class QT>
    class Bookdepth : public BaseTracker<QT>
    {
    public:
        using base = BaseTracker<QT>;
        using typename base::order_type;
        using id_type = typename QT::id_type;
        using typename base::quant_type;

        using depth_type = BaseDepth<QT>;
        using depth_ptr = std::shared_ptr<depth_type>;

    private:
        id_type ins;
        depth_ptr depth;

        /**
         * @brief Insert a quote to a queue
         *
         * @tparam Queue queue type
         * @param queue queue
         * @param price price
         * @param quant quantity
         * @return true bbo changed
         * @return false bbo not changed
         */
        template <class Queue>
        bool insert_order(Queue &queue, quant_type price, quant_type quant)
        {
            // price is better than BBO price
            bool bbo = queue.empty() || Queue::value_compare::better_price(price, queue.front().price);
            if (bbo)
            {
                // simply push front when BBO, O(1)
                queue.push_front({price, quant});
            }
            else
            {
                // find a proper insertion slot, O(log N)
                auto it = queue.lower_bound({price, quant_type{}});
                if (it->price == price)
                {
                    // price quote exists
                    it->quant += quant;
                }
                else
                {
                    // create new price quote
                    queue.insert(it, {price, quant});
                }
            }
            return bbo;
        }

        /**
         * @brief Remove a quote from a queue
         *
         * @tparam Queue queue type
         * @param queue queue
         * @param price price
         * @param quant quantity
         * @return true bbo changed
         * @return false bbo not changed
         */
        template <class Queue>
        bool erase_order(Queue &queue, quant_type price, quant_type quant)
        {
            bool bbo = false;
            // find quote in queue, O(log N)
            auto it = queue.find({price, quant_type{}});
            assert(it != queue.end());
            if (it->quant > quant)
            {
                it->quant -= quant;
            }
            else
            {
                bbo = (it == queue.begin());
                if (bbo)
                {
                    queue.pop_front();
                }
                else
                {
                    queue.erase(it);
                }
            }
            return bbo;
        }

    public:
        /**
         * @brief Construct a new Bookdepth object
         *
         * @param ins_id instrument id
         * @param depth depth
         */
        Bookdepth(id_type ins_id, depth_ptr depth)
            : ins(ins_id),
              depth(depth)
        {
        }

        void on_submit(const order_type &order) override
        {
            // depth is not changed when submitted
        }

        void on_match(const order_type &order, quant_type price, quant_type quant, quant_type tnvr, bool is_aggr) override
        {
            // only update when order is aggressor
            if (is_aggr)
            {
                auto buy = order.is_buy();
                auto price = order.price;
                auto quant = order.quant;
                auto bbo = buy ? erase_order(depth->ask(ins), price, quant) : erase_order(depth->bid(ins), price, quant);
                depth->on_trade(ins, price, quant, buy);
                depth->on_update(ins, bbo);
            }
        }

        void on_queue(const order_type &order) override
        {
            auto buy = order.is_buy();
            auto price = order.price;
            auto quant = order.quant;
            auto bbo = buy ? insert_order(depth->bid(ins), price, quant) : insert_order(depth->ask(ins), price, quant);
            depth->on_update(ins, bbo);
        }

        void on_cancel(const order_type &order, quant_type quant) override
        {
            auto buy = order.is_buy();
            auto price = order.price;
            bool bbo = buy ? erase_order(depth->bid(ins), price, quant) : erase_order(depth->ask(ins), price, quant);
            depth->on_update(ins, bbo);
        }
    };
}
